import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.decomposition import NMF
from typing import List, Dict, Tuple, Optional, Any
import logging
from collections import defaultdict
import json

logger = logging.getLogger(__name__)

class RecommendationEngine:
    """个性化推荐引擎，实现多种推荐算法"""
    
    def __init__(self):
        self.user_item_matrix = None
        self.user_similarity_matrix = None
        self.item_similarity_matrix = None
        self.user_profiles = {}
        self.item_profiles = {}
        self.config = {
            'min_similarity_threshold': 0.3,
            'max_recommendations': 10,
            'collaborative_weight': 0.6,
            'content_weight': 0.4
        }
    
    def build_user_item_matrix(self, learning_records: List[Dict[str, Any]]) -> pd.DataFrame:
        """
        构建用户-物品交互矩阵
        
        Args:
            learning_records: 学习记录列表
            
        Returns:
            用户-物品矩阵
        """
        # 提取用户-物品交互数据
        data = []
        for record in learning_records:
            user_id = record.get('user_id')
            resource_id = record.get('resource_id')
            interaction_score = self._calculate_interaction_score(record)
            
            data.append({
                'user_id': user_id,
                'resource_id': resource_id,
                'interaction_score': interaction_score
            })
        
        # 创建DataFrame
        df = pd.DataFrame(data)
        
        # 构建用户-物品矩阵
        if not df.empty:
            self.user_item_matrix = df.pivot_table(
                index='user_id', 
                columns='resource_id', 
                values='interaction_score', 
                fill_value=0
            )
        else:
            # 如果没有数据，创建空的DataFrame
            self.user_item_matrix = pd.DataFrame()
        
        return self.user_item_matrix
    
    def _calculate_interaction_score(self, record: Dict[str, Any]) -> float:
        """
        计算交互分数
        
        Args:
            record: 学习记录
            
        Returns:
            交互分数
        """
        # 基础分数
        score = 0.0
        
        # 学习时长权重 (0-1)
        study_time = record.get('study_time', 0)
        if study_time > 0:
            score += min(study_time / 60.0, 1.0) * 0.3  # 最多30分
        
        # 完成率权重 (0-1)
        completion_rate = record.get('completion_rate', 0) / 100.0
        score += completion_rate * 0.4  # 最多40分
        
        # 交互次数权重
        interaction_count = record.get('interaction_count', 0)
        score += min(interaction_count / 10.0, 1.0) * 0.2  # 最多20分
        
        # 成绩权重 (如果有)
        if 'score' in record and record['score'] is not None:
            score += (record['score'] / 100.0) * 0.1  # 最多10分
        
        return score
    
    def collaborative_filtering(self, user_id: int, n_recommendations: int = 10) -> List[Dict[str, Any]]:
        """
        协同过滤推荐算法
        
        Args:
            user_id: 目标用户ID
            n_recommendations: 推荐数量
            
        Returns:
            推荐结果列表
        """
        if self.user_item_matrix is None or self.user_item_matrix.empty:
            return []
        
        if user_id not in self.user_item_matrix.index:
            return []
        
        # 计算用户相似度
        if self.user_similarity_matrix is None:
            self.user_similarity_matrix = cosine_similarity(self.user_item_matrix)
            self.user_similarity_matrix = pd.DataFrame(
                self.user_similarity_matrix,
                index=self.user_item_matrix.index,
                columns=self.user_item_matrix.index
            )
        
        # 获取目标用户的相似用户
        user_similarities = self.user_similarity_matrix.loc[user_id]
        similar_users = user_similarities[user_similarities > self.config['min_similarity_threshold']]
        similar_users = similar_users.sort_values(ascending=False)[1:11]  # 排除自己，取前10个
        
        if similar_users.empty:
            return []
        
        # 获取目标用户已交互的物品
        user_items = self.user_item_matrix.loc[user_id]
        user_interacted_items = user_items[user_items > 0].index.tolist()
        
        # 计算推荐分数
        recommendations = defaultdict(float)
        
        for similar_user_id, similarity in similar_users.items():
            similar_user_items = self.user_item_matrix.loc[similar_user_id]
            
            for item_id in similar_user_items.index:
                if item_id not in user_interacted_items:  # 排除已交互的物品
                    item_score = similar_user_items[item_id]
                    if item_score > 0:
                        recommendations[item_id] += similarity * item_score
        
        # 排序并返回推荐结果
        sorted_recommendations = sorted(
            recommendations.items(), 
            key=lambda x: x[1], 
            reverse=True
        )[:n_recommendations]
        
        return [
            {
                'resource_id': item_id,
                'score': score,
                'algorithm': 'collaborative_filtering'
            }
            for item_id, score in sorted_recommendations
        ]
    
    def content_based_filtering(self, user_id: int, user_profile: Dict[str, Any], 
                              resources: List[Dict[str, Any]], 
                              n_recommendations: int = 10) -> List[Dict[str, Any]]:
        """
        基于内容的推荐算法
        
        Args:
            user_id: 目标用户ID
            user_profile: 用户画像
            resources: 资源列表
            n_recommendations: 推荐数量
            
        Returns:
            推荐结果列表
        """
        if not resources:
            return []
        
        # 构建资源特征矩阵
        resource_texts = []
        resource_ids = []
        
        for resource in resources:
            # 组合资源特征
            text = f"{resource.get('title', '')} {resource.get('description', '')} {resource.get('tags', '')} {resource.get('category', '')}"
            resource_texts.append(text)
            resource_ids.append(resource.get('resource_id'))
        
        # 使用TF-IDF向量化
        tfidf = TfidfVectorizer(max_features=1000, stop_words='english')
        resource_features = tfidf.fit_transform(resource_texts)
        
        # 构建用户兴趣向量
        user_interests = self._build_user_interest_vector(user_profile, tfidf)
        
        # 计算相似度
        similarities = cosine_similarity([user_interests], resource_features.toarray())[0]
        
        # 排序并返回推荐结果
        resource_similarities = list(zip(resource_ids, similarities))
        resource_similarities.sort(key=lambda x: x[1], reverse=True)
        
        return [
            {
                'resource_id': resource_id,
                'score': float(score),
                'algorithm': 'content_based'
            }
            for resource_id, score in resource_similarities[:n_recommendations]
            if score > self.config['min_similarity_threshold']
        ]
    
    def _build_user_interest_vector(self, user_profile: Dict[str, Any], 
                                  tfidf: TfidfVectorizer) -> np.ndarray:
        """
        构建用户兴趣向量
        
        Args:
            user_profile: 用户画像
            tfidf: TF-IDF向量化器
            
        Returns:
            用户兴趣向量
        """
        # 基于用户画像构建兴趣文本
        interest_text = f"{user_profile.get('major', '')} {user_profile.get('grade', '')} {user_profile.get('learning_style', '')}"
        
        # 如果有学习历史，添加历史兴趣
        if 'learning_history' in user_profile:
            for record in user_profile['learning_history']:
                interest_text += f" {record.get('name', '')} {record.get('category', '')}"
        
        # 向量化用户兴趣
        user_vector = tfidf.transform([interest_text]).toarray()[0]
        return user_vector
    
    def hybrid_recommendation(self, user_id: int, user_profile: Dict[str, Any],
                            resources: List[Dict[str, Any]], 
                            n_recommendations: int = 10) -> List[Dict[str, Any]]:
        """
        混合推荐算法
        
        Args:
            user_id: 目标用户ID
            user_profile: 用户画像
            resources: 资源列表
            n_recommendations: 推荐数量
            
        Returns:
            推荐结果列表
        """
        # 获取协同过滤推荐
        cf_recommendations = self.collaborative_filtering(user_id, n_recommendations)
        
        # 获取基于内容的推荐
        cb_recommendations = self.content_based_filtering(user_id, user_profile, resources, n_recommendations)
        
        # 合并推荐结果
        combined_scores = defaultdict(float)
        
        # 添加协同过滤分数
        for rec in cf_recommendations:
            resource_id = rec['resource_id']
            combined_scores[resource_id] += rec['score'] * self.config['collaborative_weight']
        
        # 添加基于内容的分数
        for rec in cb_recommendations:
            resource_id = rec['resource_id']
            combined_scores[resource_id] += rec['score'] * self.config['content_weight']
        
        # 排序并返回结果
        sorted_recommendations = sorted(
            combined_scores.items(),
            key=lambda x: x[1],
            reverse=True
        )[:n_recommendations]
        
        return [
            {
                'resource_id': resource_id,
                'score': score,
                'algorithm': 'hybrid'
            }
            for resource_id, score in sorted_recommendations
        ]
    
    def matrix_factorization(self, user_id: int, n_recommendations: int = 10) -> List[Dict[str, Any]]:
        """
        矩阵分解推荐算法
        
        Args:
            user_id: 目标用户ID
            n_recommendations: 推荐数量
            
        Returns:
            推荐结果列表
        """
        if self.user_item_matrix is None or self.user_item_matrix.empty:
            return []
        
        if user_id not in self.user_item_matrix.index:
            return []
        
        try:
            # 使用NMF进行矩阵分解
            n_components = min(10, min(self.user_item_matrix.shape))
            nmf = NMF(n_components=n_components, random_state=42)
            
            # 分解矩阵
            user_factors = nmf.fit_transform(self.user_item_matrix)
            item_factors = nmf.components_
            
            # 重建评分矩阵
            predicted_ratings = np.dot(user_factors, item_factors)
            predicted_df = pd.DataFrame(
                predicted_ratings,
                index=self.user_item_matrix.index,
                columns=self.user_item_matrix.columns
            )
            
            # 获取目标用户的预测评分
            user_predictions = predicted_df.loc[user_id]
            
            # 获取用户已交互的物品
            user_items = self.user_item_matrix.loc[user_id]
            user_interacted_items = user_items[user_items > 0].index.tolist()
            
            # 过滤已交互的物品并排序
            recommendations = []
            for item_id, predicted_score in user_predictions.items():
                if item_id not in user_interacted_items:
                    recommendations.append((item_id, predicted_score))
            
            recommendations.sort(key=lambda x: x[1], reverse=True)
            
            return [
                {
                    'resource_id': item_id,
                    'score': float(score),
                    'algorithm': 'matrix_factorization'
                }
                for item_id, score in recommendations[:n_recommendations]
            ]
            
        except Exception as e:
            logger.error(f"矩阵分解推荐失败: {str(e)}")
            return []
    
    def get_personalized_recommendations(self, user_id: int, user_profile: Dict[str, Any],
                                       resources: List[Dict[str, Any]], 
                                       algorithm: str = 'hybrid',
                                       n_recommendations: int = 10) -> Dict[str, Any]:
        """
        获取个性化推荐
        
        Args:
            user_id: 目标用户ID
            user_profile: 用户画像
            resources: 资源列表
            algorithm: 推荐算法 (collaborative, content, hybrid, matrix_factorization)
            n_recommendations: 推荐数量
            
        Returns:
            推荐结果
        """
        try:
            if algorithm == 'collaborative':
                recommendations = self.collaborative_filtering(user_id, n_recommendations)
            elif algorithm == 'content':
                recommendations = self.content_based_filtering(user_id, user_profile, resources, n_recommendations)
            elif algorithm == 'matrix_factorization':
                recommendations = self.matrix_factorization(user_id, n_recommendations)
            else:  # hybrid
                recommendations = self.hybrid_recommendation(user_id, user_profile, resources, n_recommendations)
            
            # 添加资源详细信息
            resource_map = {r['resource_id']: r for r in resources}
            detailed_recommendations = []
            
            for rec in recommendations:
                resource_id = rec['resource_id']
                if resource_id in resource_map:
                    detailed_rec = {
                        **rec,
                        'title': resource_map[resource_id].get('title', ''),
                        'description': resource_map[resource_id].get('description', ''),
                        'category': resource_map[resource_id].get('category', ''),
                        'difficulty_level': resource_map[resource_id].get('difficulty_level', ''),
                        'tags': resource_map[resource_id].get('tags', '')
                    }
                    detailed_recommendations.append(detailed_rec)
            
            return {
                'user_id': user_id,
                'algorithm': algorithm,
                'recommendations': detailed_recommendations,
                'total_count': len(detailed_recommendations)
            }
            
        except Exception as e:
            logger.error(f"获取个性化推荐失败: {str(e)}")
            return {
                'user_id': user_id,
                'algorithm': algorithm,
                'recommendations': [],
                'total_count': 0,
                'error': str(e)
            }
    
    def update_user_profile(self, user_id: int, new_interaction: Dict[str, Any]):
        """
        更新用户画像
        
        Args:
            user_id: 用户ID
            new_interaction: 新的交互记录
        """
        if user_id not in self.user_profiles:
            self.user_profiles[user_id] = {
                'interactions': [],
                'preferences': {},
                'last_updated': None
            }
        
        # 添加新的交互记录
        self.user_profiles[user_id]['interactions'].append(new_interaction)
        
        # 更新偏好
        self._update_user_preferences(user_id, new_interaction)
    
    def _update_user_preferences(self, user_id: int, interaction: Dict[str, Any]):
        """
        更新用户偏好
        
        Args:
            user_id: 用户ID
            interaction: 交互记录
        """
        preferences = self.user_profiles[user_id]['preferences']
        
        # 更新类别偏好
        category = interaction.get('category')
        if category:
            if category not in preferences:
                preferences[category] = 0
            preferences[category] += 1
        
        # 更新难度偏好
        difficulty = interaction.get('difficulty_level')
        if difficulty:
            if 'difficulty_preferences' not in preferences:
                preferences['difficulty_preferences'] = {}
            if difficulty not in preferences['difficulty_preferences']:
                preferences['difficulty_preferences'][difficulty] = 0
            preferences['difficulty_preferences'][difficulty] += 1
        
        # 更新学习时间偏好
        study_time = interaction.get('study_time', 0)
        if study_time > 0:
            if 'avg_study_time' not in preferences:
                preferences['avg_study_time'] = study_time
                preferences['study_time_count'] = 1
            else:
                total_time = preferences['avg_study_time'] * preferences['study_time_count']
                preferences['study_time_count'] += 1
                preferences['avg_study_time'] = (total_time + study_time) / preferences['study_time_count']